import 'reflect-metadata'

// 定义一个依赖注入的容器类
class DIContainer {
  // 定义一个存储所有服务的Map对象
  private services = new Map<string, any>()

  // 注册服务 把服务放到容器中
  register(name: string, Service: any) {
    this.services.set(name, Service) // 保存服务到 Map 中
  }

  // 解析 从容器里面取出对应的服务
  resolve<T>(name: string) {
    const Service = this.services.get(name) // 获取对应的服务
    if (Service instanceof Function) { // 是一个类
      // 获取构造函数依赖的参数类型组成的数组 [ Engine ]
      const dependencies = Reflect.getMetadata('design:paramtypes', Service) ?? []
      // 递归调用 resolve 函数 获取当前类实例化时所需要(依赖)的实例
      const injections = dependencies.map(dependency => this.resolve(dependency.name))
      // 创建并返回类的实例
      return new Service(...injections) // 获取值
    } else if (Service.useFactory) { // 是一个工厂
      const { inject } = Service // 获取注入的值
      return Service.useFactory(...inject) // 调用工厂获取值
    } else if (Service.useValue) { // 是一个值
      return Service.useValue // 获取值
    }

  }
}

// 只要给类添加了类装饰器
// 会自动添加 design:paramtypes 元数据
function Injectable(target) { }

@Injectable
class Oil {
  constructor(private num: number) {

  }
  start() {
    console.log(`石油启动了`, this.num)
  }
}

@Injectable
class Engine {
  constructor(private oil: Oil, private num: number) {

  }
  start() {
    this.oil.start()
    console.log(`发动机启动了`, this.num)
  }
}
// 如果 Engine 没有写构造函数内 design:paramtypes 元数据的值是 undefined
// 如果 Engine 写了构造函数则 design:paramtypes 元数据的值是一个数组
// console.log('Engine', Reflect.getMetadata('design:paramtypes', Engine))

@Injectable
class Car {
  // 没有 IOC 的时候需要手动处理依赖的类实例化操作
  // 有 IOC 时只需要声明所需要的依赖 ( 容器会根据依赖的类型自动实例化并传递相应的实例 )
  constructor(private engine: Engine) { }

  drive() {
    this.engine.start()
    console.log('汽车启动了')
  }
}

const container = new DIContainer() // 创建容器

// 将类保存到容器中
container.register('Oil', {
  provide: 'Oil',
  inject: [100], // 注入到工厂函数的值 或 token令牌(目前还没有实现)
  useFactory: (number) => {
    return new Oil(number)
  }
})
container.register('Engine', {
  provide: 'Engine',
  useValue: new Engine(new Oil(200), 66) // 传递的一个值
})
container.register('Car', Car)

// 基于 token 令牌从容器中取出对应的实例
// 实例的创建及依赖的管理交给容器来管理 
const car = container.resolve<Car>('Car')
car.drive()

/**
 * 核心思想
 * 1. 实例化一个 DIContainer 容器实例
 * 2. 调用容器实例的 register 将需要管理 token 令牌和对应的类保存到容器中
 * 3. 使用容器实例的 resolve 方法传递对应的 token 令牌获取对应的实例
 *   3.1 基于令牌取出对应的类
 *   3.2 使用 Reflect.getMetadata 获取 design:paramtypes 元数据里面保存的是 3.1 类实例化过程依赖的类型组成的数组
 *   3.3 遍历依赖的类型 递归调用 resolve 函数获取依赖的实例
 *   3.4 实例化 3.1 取出的类
 */